#include <stdio.h>
#include <stdlib.h>
#include <dos.h>
#include "MPX_SUPT.h"
#include "pcb.h"
#include "R3.h"
#include "procs-r3.h"
#include "R2.h"
#include "TRMDRIVE.H"

pcb *to_run;
unsigned short ss_save = NULL;
unsigned short sp_save = NULL;
unsigned short ss_save2 = NULL;
unsigned short sp_save2 = NULL;
unsigned short new_ss;
unsigned short new_sp;
unsigned long stack_top;
unsigned char sys_stack[SYS_STACK_SIZE];

params *this_param = NULL;

//R6 declarations

iocb *com_iocb;
iocb *terminal_iocb;
iod *to_remove;

//for temp use in scheduler
int sched_device_id;
iod *sched_new_iod;
iod *sched_current_tail;
iocb *sched_device_iocb;

//End R6 declarations

void interrupt dispatch(){ //NO VARIABLES EXCEPT FOR STATIC
	
	//save SS and SP only once
	if(sp_save == NULL) {
		disable();
		ss_save = _SS;
		sp_save = _SP;
		enable();
	}
	//get head of ready queue, if it exists
	to_run = get_ready_head();
	if(to_run != NULL){
		//remove pcb and set to running
		remove_pcb(to_run);
		to_run->state = PROC_STATE_RUNNING;
		//set SP to top of pcb stack
		disable();
		new_ss = FP_SEG(to_run->stack_top);
		new_sp = FP_OFF(to_run->stack_top);
		_SS = new_ss;
		_SP = new_sp;
		enable();
	}
	else{
		disable();
		to_run = NULL;
		_SS = ss_save;
		_SP = sp_save;	
		ss_save = NULL;
		sp_save = NULL;
		enable();
	}
}

void interrupt sys_call(){ //NO VARIABLES EXCEPT FOR STATIC
	//1) you must save the SS & SP back into stack top
	to_run->stack_top = (unsigned char*) MK_FP(_SS, _SP);
	//2) switch to a temporaary stack
	
	//save SS and SP
	this_param = (params*)(to_run->stack_top + sizeof(context));
	//change to temp stack
	//start r6 temp stack additions - see next line
	//the lines between the start and end comments were added in r6 - they switch to a temp stack
	//for some reason, we never did this in r3 but it still worked. probably because this function
	//didn't call any intensive functions so there was no danger of the stack getting too full.
	//I am adding it now just in case because more happens in this handler in r6. from r3 slides
	//disable();
	ss_save2 = _SS;
	sp_save2 = _SP;
	//stack_top = sys_stack + SYS_STACK_SIZE + 1 - sizeof(context);
	//new_ss = FP_SEG(stack_top);
	//new_sp = FP_OFF(stack_top);
	new_ss = FP_SEG(sys_stack);
	new_sp = FP_OFF(sys_stack) + SYS_STACK_SIZE;
	_SS = new_ss;
	_SP = new_sp;
	//enable();
	//end r6 temp stack additions
	
	//start r6 io devices handling
	trm_getc();//flush terminal buffer into mpx buffer
	if (com_iocb->event_flag == 1) {
		com_iocb->event_flag = 0;
		if (com_iocb->count > 0) {
			to_remove = com_iocb->head;
			unblock_pcb(to_remove->pcb_name, "\0");
			com_iocb->head = to_remove->next;
			sys_free_mem(to_remove);//remove, free
			com_iocb->count--;
			if (com_iocb->head != NULL) {
				//next line might need to be io_scheduler();, don't think so though
				process_request_now(COM_PORT);//process next io request somehow
			}
		}
	}
	if (terminal_iocb->event_flag == 1) {
		terminal_iocb->event_flag = 0;
		if (terminal_iocb->count > 0) {
			to_remove = terminal_iocb->head;
			unblock_pcb(to_remove->pcb_name, "\0");
			terminal_iocb->head = to_remove->next;
			sys_free_mem(to_remove);
			terminal_iocb->count--;
			if (terminal_iocb->head != NULL) {
				//next line might need to be io_scheduler();, don't think so though
				process_request_now(TERMINAL);//process next io request somehow
			}
		}
	}
	//end r6 io devices handling
	
	if(this_param->op_code == IDLE){
		to_run->state = PROC_STATE_READY;
		insert_pcb(to_run, PROC_STATE_READY, PROC_NOT_SUSP);
	}
	else if(this_param->op_code == EXIT){
		free_pcb(to_run);
		to_run = NULL;
	}
	else if(this_param->op_code == READ) {
		io_scheduler();
	}
	else if(this_param->op_code == WRITE) {
		io_scheduler();
	}
	else if(this_param->op_code == CLEAR) {
		io_scheduler();
	}
	else if(this_param->op_code == GOTOXY) {
		io_scheduler();
	}
	
	//next four lines are r6 additions (two comments, two lines of code)
	//context of pcb->AX = result; 0 or negative if invalid ??if needed
	//are these needed? restore stack to the pcb's 
	//_SS = ss_save2;
	//_SP = sp_save2;
	
	dispatch();
}


//START R6 CODE

//initialize iocbs for the two devices managed by MPX - com port and terminal
//call from main before any io begins so that queues are set up
//latest edit - now this function opens the devices as well (might change this back later)
void init_iocbs() {
	com_iocb = (iocb*)sys_alloc_mem(sizeof(iocb));
	com_iocb->event_flag = 0;
	com_iocb->count = 0;
	com_iocb->head = NULL;
	com_iocb->tail = NULL;
	com_open(&com_iocb->event_flag, 1200);
	
	terminal_iocb = (iocb*)sys_alloc_mem(sizeof(iocb));
	terminal_iocb->event_flag = 0;
	terminal_iocb->count = 0;
	terminal_iocb->head = NULL;
	terminal_iocb->tail = NULL;
	trm_open(&terminal_iocb->event_flag);
}

//Follows outline in slides about "processing IO request"
void io_scheduler() {
	sched_device_id = this_param->device_id; //global parameter described in slides
	sched_new_iod = (iod*)sys_alloc_mem(sizeof(iod));
	sched_new_iod->requester = to_run; //COP
	strcpy(sched_new_iod->pcb_name, to_run->name);//GET RID OF LIBRARY CALL??
	sched_new_iod->trans_buffer = this_param->buf_addr; //manual says buffer is char* so cast used
	sched_new_iod->buffer_count = this_param->count_addr;
	sched_new_iod->request_type = this_param->op_code;
	sched_new_iod->next = NULL;
	
	//set device_iocb accordingly, either to com_iocb or terminal_iocb (both globals in this file)
	if (sched_device_id == COM_PORT) {
		sched_device_iocb = com_iocb;
	}
	else if (sched_device_id == TERMINAL) {
		sched_device_iocb = terminal_iocb;
	}
	
	if (sched_device_iocb->count == 0) {//gets count of the device's IOCB
		sched_device_iocb->head = sched_new_iod;
		sched_device_iocb->tail = sched_new_iod;
		sched_device_iocb->count = 1;
		process_request_now(sched_device_id);//Process request immediately
	}
	else {
		sched_current_tail = sched_device_iocb->tail;
		sched_current_tail->next = sched_new_iod;
		sched_device_iocb->tail = sched_new_iod;
		sched_device_iocb->count++;
	}
	
	block_pcb(to_run->name, "\0");
}

void process_request_now(int device) {
	iod *request;
	int type;
	if (device == COM_PORT) {
		request = com_iocb->head;
		type = request->request_type;
		if (type == READ) {
			com_read(request->trans_buffer, request->buffer_count);
		}
		else if(type == WRITE) {
			com_write(request->trans_buffer, request->buffer_count);
		}
	}
	else if (device == TERMINAL) {
		request = terminal_iocb->head;
		type = request->request_type;
		if (type == READ) {
			trm_read(request->trans_buffer, request->buffer_count);
		}
		else if(type == WRITE) {
			trm_write(request->trans_buffer, request->buffer_count);
		}
		else if (type == CLEAR) {
			trm_clear();
		}
		else if (type == GOTOXY) {
			trm_gotoxy(0,0);//DON'T KNOW HOW TO GET X,Y YET - I THINK FIRST TWO ELEMENTS OF request's trans_buffer
			//if ((request->trans_buffer[0] >= 0) && (request->trans_buffer[1] >= 0))
			//	trm_gotoxy(request->trans_buffer[0], request->trans_buffer[1]);
		}
	}
}

void mpx_io_cleanup() {
	iod *curr;
	trm_close();
	com_close();
	curr = terminal_iocb->head;
	while (curr != NULL) {
		terminal_iocb->head = curr->next;
		sys_free_mem(curr);
		curr = terminal_iocb->head;
	}
	sys_free_mem(terminal_iocb);
	curr = com_iocb->head;
	while (curr != NULL) {
		com_iocb->head = curr->next;
		sys_free_mem(curr);
		curr = com_iocb->head;
	}
	sys_free_mem(com_iocb);
}


//END R6 CODE


/*
void load_procs(){
	pcb *np;
	context *npc;
	np = setup_PCB("name1", 1, 0);
	if(np != NULL){
		npc = (context*) np->stack_top; //notes say stack_top
		npc->IP = FP_OFF(&test1_R3);
		npc->CS = FP_SEG(&test1_R3);
		npc->FLAGS = 0x200;
		npc->DS = _DS;
		npc->ES = _ES;
		insert_pcb(np, PROC_STATE_READY, PROC_NOT_SUSP);
	}
	
	np = setup_PCB("name2", 1, 0);
	if(np != NULL){
		npc = (context*) np->stack_top; //notes say stack_top
		npc->IP = FP_OFF(&test2_R3);
		npc->CS = FP_SEG(&test2_R3);
		npc->FLAGS = 0x200;
		npc->DS = _DS;
		npc->ES = _ES;
		insert_pcb(np, PROC_STATE_READY, PROC_NOT_SUSP);
	}
	
	np = setup_PCB("name3", 1, 0);
	if(np != NULL){
		npc = (context*) np->stack_top; //notes say stack_top
		npc->IP = FP_OFF(&test3_R3);
		npc->CS = FP_SEG(&test3_R3);
		npc->FLAGS = 0x200;
		npc->DS = _DS;
		npc->ES = _ES;
		insert_pcb(np, PROC_STATE_READY, PROC_NOT_SUSP);
	}
	
	np = setup_PCB("name4", 1, 0);
	if(np != NULL){
		npc = (context*) np->stack_top; //notes say stack_top
		npc->IP = FP_OFF(&test4_R3);
		npc->CS = FP_SEG(&test4_R3);
		npc->FLAGS = 0x200;
		npc->DS = _DS;
		npc->ES = _ES;
		insert_pcb(np, PROC_STATE_READY, PROC_NOT_SUSP);
	}
	
	np = setup_PCB("name5", 1, 0);
	if(np != NULL){
		npc = (context*) np->stack_top; //notes say stack_top
		npc->IP = FP_OFF(&test5_R3);
		npc->CS = FP_SEG(&test5_R3);
		npc->FLAGS = 0x200;
		npc->DS = _DS;
		npc->ES = _ES;
		insert_pcb(np, PROC_STATE_READY, PROC_NOT_SUSP);
	}
}
*/


struct pcb *get_to_run(){
	return to_run;
}
/*
void kill_to_run(){
	to_run = NULL;
}
*/